/*
 * CombineSourceBuilder.cpp
 *
 *  Created on: 22 juil. 2011
 *      Author: Pierrick Caillon <pierrick@megami.fr>
 */

#include "CombineSourceBuilder.h"
#include <TagCompound.h>
#include <TagByteArray.h>
#include <TagByte.h>
#include <TagShort.h>
#include <TagInt.h>
#include <TagFactory.h>
#include <string.h>
#include <math.h>
#include <algorithm>

const char* fileCenterX = "xCenter";
const char* fileCenterZ = "zCenter";
const char* fileWidth = "width";
const char* fileHeight = "height";
const char* fileColors = "colors";
const char* fileZoom = "scale";

namespace nbt = NamedBinaryTag;
const nbt::TagCompound &DataTag(const nbt::DataFile &dataFile);
char *Bytes(const nbt::TagCompound &data, const char *name);
__int16_t Short(const nbt::TagCompound &data, const char *name);
__int32_t Int(const nbt::TagCompound &data, const char *name);
__int8_t Byte(const nbt::TagCompound &data, const char *name);

nbt::TagByteArray*
Bytes(const char *name, char* value, size_t length);
nbt::TagShort* Short(const char *name, __int16_t value);
nbt::TagInt* Int(const char *name, __int32_t value);
nbt::TagByte* Byte(const char *name, __int8_t value);

CombineSourceBuilder::CombineSourceBuilder() :
	minZoom(INT_MAX)
{
}

/**
 * @remarks The image data array which is previously created by CreateImage is freed.
 */
nbt::ITag *CombineSourceBuilder::MakeDataTag(
		const std::list<nbt::DataFile*> & dataFiles, char *image,
		const Rect bounds)
{
	nbt::TagCompound *dataTag =
			dynamic_cast<nbt::TagCompound*> (nbt::TagFactory::CreateTag(
					nbt::TAG_COMPOUND));
	dataTag->set_Name("data");
	Size size(ComputeImageSize(bounds));
	Point center(bounds.X() + bounds.W() / 2, bounds.Y() + bounds.H() / 2);
	int minZoom = this->minZoom;
	__int8_t zoom = 0;
	while (minZoom > 1)
	{
		zoom++;
		minZoom = minZoom >> 1;
	}
	dataTag->Add(Short(fileWidth, size.W()));
	dataTag->Add(Short(fileHeight, size.H()));
	dataTag->Add(Int(fileCenterX, center.X()));
	dataTag->Add(Int(fileCenterZ, center.Y()));
	dataTag->Add(Byte(fileZoom, zoom));
	dataTag->Add(
			Byte("dimension", Byte(DataTag(*(dataFiles.front())), "dimension")));
	dataTag->Add(Bytes(fileColors, image, size.W() * size.H()));
	delete[] image;
	return dataTag;
}

char *CombineSourceBuilder::CreateImage(
		const std::list<nbt::DataFile*> & dataFiles, const Rect bounds) const
{
	const Size finalSize(ComputeImageSize(bounds));
	const size_t dataSize = finalSize.W() * finalSize.H();
	char *data = new char[dataSize];
	memset(data, 0, finalSize.W() * finalSize.H());
	int num = 0;
	for (std::list<nbt::DataFile*>::const_iterator it = dataFiles.begin(); it
			!= dataFiles.end(); ++it)
	{
		const nbt::TagCompound &imageRoot = DataTag(**it);
		int scale = 1 << Byte(imageRoot, fileZoom);
		int relScale = scale / minZoom;
		Point center(Int(imageRoot, fileCenterX), Int(imageRoot, fileCenterZ));
		Size sizeImage(Short(imageRoot, fileWidth),
				Short(imageRoot, fileHeight));
		Size sizeTerran(sizeImage.W() * scale, sizeImage.H() * scale);
		Point topLeft(center.X() - sizeTerran.W() / 2,
				center.Y() - sizeTerran.H() / 2);
		int startX = (int) round((topLeft.X() - bounds.X()) / (float) minZoom);
		int startY = (int) round((topLeft.Y() - bounds.Y()) / (float) minZoom);
		char *imageData = Bytes(imageRoot, fileColors);
		for (int y = 0; y < sizeImage.H(); y++)
		{
			int startOffset = (y * relScale + startY) * finalSize.W();
			int imageOffset = y * sizeImage.W();
			for (int x = 0; x < sizeImage.W(); x++)
			{
				if (imageData[imageOffset + x] != 0)
				{
					for (int sX = 0; sX < relScale; sX++)
						for (int sY = 0; sY < relScale; sY++)
						{
							data[startOffset + (sY * finalSize.W()) + startX
									+ x * relScale + sX]
									= imageData[imageOffset + x];
						}
				}
			}
		}
		num++;
	}
	return data;
}

std::list<nbt::DataFile*> CombineSourceBuilder::LoadFiles(iterator & it) const
{
	std::list<nbt::DataFile*> files;
	ListCleaner cleaner(files);
	while (it.next())
	{
		if (it.current() == NULL)
			continue;
		nbt::DataFile *dataFile = new nbt::DataFile();
		dataFile->Load(*(it.current()));
		files.push_back(dataFile);
	}
	cleaner.Cancel();
	return files;
}

Size CombineSourceBuilder::ComputeImageSize(const Rect bounds) const
{
	return Size((int) ceil(bounds.W() / (double) this->minZoom),
			(int) ceil(bounds.H() / (double) this->minZoom));
}

Rect CombineSourceBuilder::ComputeBounds(
		const std::list<nbt::DataFile*> & dataFiles)
{
	Rect bounds;
	bool first = true;
	for (std::list<nbt::DataFile*>::const_iterator it = dataFiles.begin(); it
			!= dataFiles.end(); ++it)
	{
		const nbt::TagCompound &imageRoot = DataTag(**it);
		int scale = 1 << Byte(imageRoot, fileZoom);
		minZoom = std::min(minZoom, scale);
		Point center(Int(imageRoot, fileCenterX), Int(imageRoot, fileCenterZ));
		Size sizeImage(Short(imageRoot, fileWidth),
				Short(imageRoot, fileHeight));
		Size sizeTerran(sizeImage.W() * scale, sizeImage.H() * scale);
		Point topLeft(center.X() - sizeTerran.W() / 2,
				center.Y() - sizeTerran.H() / 2);
		if (first)
			bounds = Rect(topLeft, sizeTerran);
		else
			bounds.Union(Rect(topLeft, sizeTerran));
		first = false;
	}
	return bounds;
}

__int32_t Int(const nbt::TagCompound & data, const char *name)
{
	return (dynamic_cast<nbt::TagInt*> (data[name])->get_Value());
}

__int8_t Byte(const nbt::TagCompound & data, const char *name)
{
	return (dynamic_cast<nbt::TagByte*> (data[name])->get_Value());
}

char *Bytes(const nbt::TagCompound & data, const char *name)
{
	return (dynamic_cast<nbt::TagByteArray*> (data[name])->get_Value());
}

const nbt::TagCompound & DataTag(const nbt::DataFile & dataFile)
{
	return *dynamic_cast<nbt::TagCompound*> ((*dataFile.get_RootTag())["data"]);
}

__int16_t Short(const nbt::TagCompound & data, const char *name)
{
	return (dynamic_cast<nbt::TagShort*> (data[name])->get_Value());
}

nbt::TagInt *Int(const char *name, __int32_t value)
{
	nbt::TagInt *tag = dynamic_cast<nbt::TagInt*> (nbt::TagFactory::CreateTag(
			nbt::TAG_INT));
	tag->set_Name(name);
	tag->set_Value(value);
	return tag;
}

nbt::TagByte* Byte(const char *name, __int8_t value)
{
	nbt::TagByte *tag =
			dynamic_cast<nbt::TagByte*> (nbt::TagFactory::CreateTag(
					nbt::TAG_BYTE));
	tag->set_Name(name);
	tag->set_Value(value);
	return tag;
}

nbt::TagByteArray* Bytes(const char *name, char *value, size_t length)
{
	nbt::TagByteArray *tag =
			dynamic_cast<nbt::TagByteArray*> (nbt::TagFactory::CreateTag(
					nbt::TAG_BYTEARRAY));
	tag->set_Name(name);
	tag->set_Value(value, length);
	return tag;
}

nbt::TagShort *Short(const char *name, __int16_t value)
{
	nbt::TagShort *tag =
			dynamic_cast<nbt::TagShort*> (nbt::TagFactory::CreateTag(
					nbt::TAG_SHORT));
	tag->set_Name(name);
	tag->set_Value(value);
	return tag;
}

CombineSourceBuilder::ListCleaner::ListCleaner(
		std::list<NamedBinaryTag::DataFile*> list) :
	ok(false), list(list)
{
}

CombineSourceBuilder::ListCleaner::~ListCleaner()
{
	if (!ok)
	{
		for (std::list<nbt::DataFile*>::iterator it = list.begin(); it
				!= list.end(); it++)
		{
			delete *it;
		}
	}
}

void CombineSourceBuilder::ListCleaner::Cancel()
{
	ok = true;
}

